#include "Sequence.hxx"
#include "VideoView.hxx"
#include "TrackedObject.hxx"
#include "Project.hxx"

Sequence::Sequence(QObject *parent) : QObject(parent)
{
	_view = nullptr;
    _videoHeight = 0;
    _videoWidth = 0;

	_videoDuration = 1.0;
	_currentTimeStamp = 0.0;
	_currentFrameIndex = 0;
	_frameRate = 0.0;

	_seekMutex.lock();
	_seekPos = 0;
	_seekFlag = false;
	_seekMutex.unlock();
	_nextTrackedObjectId = 0;
}

Sequence::~Sequence()
{
	for (int i = 0; i < _trackingObjects.size(); i++)
	{
		TrackedObject* obj = _trackingObjects.at(i);
		delete obj;
	}
	_trackingObjects.clear();

	for (int i = 0; i < _trackedObjects.size(); i++)
	{
		TrackedObject* obj = _trackedObjects.at(i);

		delete obj;
	}
	_trackedObjects.clear();
}



void Sequence::seekVideoPosition(double pos)
{
	_seekMutex.lock();
	_seekPos = pos;
	_seekFlag = true;
	_framePool.clear();
	_trackedFramePool.clear();
	_seekMutex.unlock();
}

void Sequence::updateNextTrackedObjectId()
{
	int maxId = 0;

	for (int i = 0; i < _trackedObjects.size(); i++)
	{
		TrackedObject* obj = _trackedObjects.at(i);

		if (obj->getObjectId() > maxId)
			maxId = obj->getObjectId();
	}

	_nextTrackedObjectId = maxId + 1;

}

void Sequence::prepareTrackingObjects()
{
	_trackingObjects.clear();

	if (_view)
	{
		std::vector<cv::Rect2d> cvrects = _view->getCvRects();

		for (int i = 0; i < cvrects.size(); i++)
		{			
			cv::Rect2d rect = cvrects.at(i);

			TrackedObject* object = new TrackedObject();
			object->setTemplateBox(rect);
			object->setObjectId(_nextTrackedObjectId++);
			object->setStartFrameIndex(_currentFrameIndex);
			object->setEndFrameIndex(_currentFrameIndex);

            object->setClassIndex(0);

			_trackingObjects.push_back(object);
			
		}

		_view->clearRects();
	}
}

void Sequence::moveTrackedObjects()
{
	Project* project = _view->getProject();

	for (int i = 0; i < _trackingObjects.size(); i++)
	{
		TrackedObject* tobj = _trackingObjects.at(i);
		tobj->setObjectId(project->getNextObjectId());
		_trackedObjects.push_back(tobj);
	}

	_trackingObjects.clear();
}

void Sequence::makeSingleFrameObjects()
{
	prepareTrackingObjects();

	Project* project = _view->getProject();

	for (int i = 0; i < _trackingObjects.size(); i++)
	{
		TrackedObject* tobj = _trackingObjects.at(i);

		tobj->setObjectId(project->getNextObjectId());

		tobj->pushRect(tobj->getStartFrameIndex(),tobj->getTemplateBox());

		_trackedObjects.push_back(tobj);
	}

	_trackingObjects.clear();
}

void Sequence::makeNegativeSampleFrame()
{
	Project* project = _view->getProject();
	
	TrackedObject* tobj = new TrackedObject();
	
	tobj->setObjectId(project->getNextObjectId());

	int w = getVideoWidth();
	int h = getVideoHeight();

	tobj->setClassIndex(project->getNegativeSampleClassId());
	tobj->setStartFrameIndex(_currentFrameIndex);
	tobj->setEndFrameIndex(_currentFrameIndex);
	
	tobj->pushRect(_currentFrameIndex, cv::Rect2d(20.0, 40.0, w - 40, h - 60 ));
	
	_trackedObjects.push_back(tobj);

	_trackingObjects.clear();
}

void Sequence::desectAllTrackedObjects()
{
	for (int i = 0; i < _trackedObjects.size(); i++)
	{
		TrackedObject* obj = _trackedObjects.at(i);
		obj->deselect();
	}
}

void Sequence::deleteSelectedObject()
{
	int index = -1;
	for (int i = 0; i < _trackedObjects.size(); i++)
	{
		TrackedObject* obj = _trackedObjects.at(i);
		if (obj->isSelected())
		{
			index = i;
			break;
		}
	}

	if (index > -1)
	{
		TrackedObject* obj = _trackedObjects.at(index);
		delete obj;
		_trackedObjects.erase(_trackedObjects.begin() + index);
	}
}

void Sequence::startObject(int findex)
{
	int index = -1;
	for (int i = 0; i < _trackedObjects.size(); i++)
	{
		TrackedObject* obj = _trackedObjects.at(i);
		if (obj->isSelected())
		{
			index = i;
			break;
		}
	}

	if (index > -1)
	{
		TrackedObject* obj = _trackedObjects.at(index);
		int sindex = obj->getStartFrameIndex();

		if (findex < sindex )
		{
			cv::Rect2d rect = obj->getRect(sindex);
			for (int i = findex; i < sindex; i++)
				obj->pushRect(i, rect);

			obj->setStartFrameIndex(findex);
		}
		else if (findex > sindex)
		{
			for (int i = sindex; i < findex; i++)
				obj->eraseRect(i);

			obj->setStartFrameIndex(findex);
		}
	}
}

void Sequence::endObject(int findex)
{
	int index = -1;
	for (int i = 0; i < _trackedObjects.size(); i++)
	{
		TrackedObject* obj = _trackedObjects.at(i);
		if (obj->isSelected())
		{
			index = i;
			break;
		}
	}

	if (index > -1)
	{
		TrackedObject* obj = _trackedObjects.at(index);
		int eindex = obj->getEndFrameIndex();

		if (findex < eindex)
		{
			for (int i = findex + 1; i <= eindex; i++)
				obj->eraseRect(i);

			obj->setEndFrameIndex(findex);
		}
		else if (findex > eindex)
		{
			cv::Rect2d rect = obj->getRect(eindex);
			for (int i = eindex + 1; i <= findex; i++)
				obj->pushRect(i, rect);

			obj->setEndFrameIndex(findex);
		}		
	}
}

void Sequence::updateObjectClass(int objectId, int classIndex)
{
    for(int i = 0; i < _trackedObjects.size(); i++)
    {
        TrackedObject* obj = _trackedObjects.at(i);

        if( obj->getObjectId() ==  objectId )
        {
            obj->setClassIndex(classIndex);
            break;
        }
    }
}

void Sequence::updateObjectStartFrame(int objectId, int startFrame)
{
	for (int i = 0; i < _trackedObjects.size(); i++)
	{
		TrackedObject* obj = _trackedObjects.at(i);

		if (obj->getObjectId() != objectId) continue;

		if (startFrame < obj->getStartFrameIndex())
		{
			cv::Rect2d rect = obj->getRect(obj->getStartFrameIndex());

			// insert frames 
			for (int i = startFrame; i < obj->getStartFrameIndex(); i++)
				obj->pushRect(i, rect);

			obj->setStartFrameIndex(startFrame);
		}
		else if (startFrame > obj->getStartFrameIndex() && startFrame < obj->getEndFrameIndex())
		{
			// erase frames
			for (int i = obj->getStartFrameIndex(); i < startFrame; i++)
				obj->eraseRect(i);

			obj->setStartFrameIndex(startFrame);
		}
	}
}

void Sequence::updateObjectEndFrame(int objectId, int endFrame)
{
	for (int i = 0; i < _trackedObjects.size(); i++)
	{
		TrackedObject* obj = _trackedObjects.at(i);

		if (obj->getObjectId() != objectId) continue;

		if (endFrame > obj->getEndFrameIndex())
		{
			cv::Rect2d rect = obj->getRect(obj->getEndFrameIndex());
			for (int i = obj->getEndFrameIndex() + 1; i <= endFrame; i++)
				obj->pushRect(i, rect);

			obj->setEndFrameIndex(endFrame);
		}
		else if (endFrame < obj->getEndFrameIndex())
		{
			for (int i = obj->getEndFrameIndex(); i > endFrame; i--)
				obj->eraseRect(i);

			obj->setEndFrameIndex(endFrame);
		}
	}
}

void Sequence::mergeTrackedObjects(const std::vector<TrackedObject*>& objects)
{
	TrackedObject* resultObject = new TrackedObject();

	for (int i = 0; i < objects.size(); i++)
	{
		TrackedObject* obj = objects.at(i);

		int osIndex = obj->getStartFrameIndex();
		int oeIndex = obj->getEndFrameIndex();

		if (resultObject->getObjectId() == -1)
		{
			resultObject->setObjectId(obj->getObjectId());
			resultObject->setStartFrameIndex(obj->getStartFrameIndex());
			resultObject->setEndFrameIndex(obj->getEndFrameIndex());
			resultObject->setClassIndex(obj->getClassIndex());
		}
		else
		{
			int rsIndex = resultObject->getStartFrameIndex();
			int reIndex = resultObject->getEndFrameIndex();

			if (osIndex < rsIndex)
				resultObject->setStartFrameIndex(osIndex);

			if (oeIndex > reIndex)
				resultObject->setEndFrameIndex(oeIndex);
		}

		for (int j = osIndex; j <= oeIndex; j++)
		{
			cv::Rect2d orect = obj->getRect(j);
			cv::Rect2d rrect = resultObject->getRect(j);

			if (fabs(rrect.x) < 0.01 && fabs(rrect.y) < 0.01 && fabs(rrect.width) < 0.01 && fabs(rrect.height) < 0.01)
			{
				resultObject->pushRect(j, orect);
			}
			else
			{
				double oltx = orect.x;
				double olty = orect.y;
				double orbx = orect.x + orect.width;
				double orby = orect.y + orect.height;

				double rltx = rrect.x;
				double rlty = rrect.y;
				double rrbx = rrect.x + rrect.width;
				double rrby = rrect.y + rrect.height;

				double ltx = 0.0;
				double lty = 0.0;
				double rbx = 0.0;
				double rby = 0.0;

				if (oltx < rltx)
					ltx = oltx;
				else
					ltx = rltx;

				if (olty < rlty)
					lty = olty;
				else
					lty = rlty;

				if (orbx > rrbx)
					rbx = orbx;
				else
					rbx = rrbx;

				if (orby > rrby)
					rby = orby;
				else
					rby = rrby;

				cv::Rect2d rect;
				rect.x = ltx;
				rect.y = lty;
				rect.width = rbx - ltx;
				rect.height = rby - lty;

				resultObject->pushRect(j, rect);
			}
		}
	}

	// Check the empty frames;
	cv::Rect2d lastRect;
	for (int i = resultObject->getStartFrameIndex(); i <= resultObject->getEndFrameIndex(); i++)
	{
		cv::Rect2d rect = resultObject->getRect(i);

		if (fabs(rect.x) < 0.01 && fabs(rect.y) < 0.01 && fabs(rect.width) < 0.01 && fabs(rect.height) < 0.01)
		{
			resultObject->pushRect(i, lastRect);
		}
		else
		{
			lastRect = rect;
		}
	}

	// remove old objects
	for (int i = 0; i < objects.size(); i++)
	{
		TrackedObject* obj = objects.at(i);

		for (int j = 0; j < _trackedObjects.size(); j++)
		{
			TrackedObject* vobj = _trackedObjects.at(j);

			if (obj == vobj)
			{
				_trackedObjects.erase(_trackedObjects.begin() + j);
				delete vobj;
				break;
			}
		}
	}

	// add new object
	_trackedObjects.push_back(resultObject);
}